/*
 * Copyright (C) 2008 Universidade Federal de Campina Grande
 *  
 * This file is part of OurGrid. 
 *
 * OurGrid is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free 
 * Software Foundation, either version 3 of the License, or (at your option) 
 * any later version. 
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT 
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
 * for more details. 
 * 
 * You should have received a copy of the GNU Lesser General Public License 
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 * 
 */
package org.ourgrid.peer.controller;

import static org.ourgrid.common.util.CommonUtils.checkKey;

import org.ourgrid.common.interfaces.LocalWorkerProviderClient;
import org.ourgrid.common.interfaces.RemoteWorkerProvider;
import org.ourgrid.common.interfaces.RemoteWorkerProviderClient;
import org.ourgrid.common.interfaces.Worker;
import org.ourgrid.common.interfaces.management.RemoteWorkerManagement;
import org.ourgrid.common.interfaces.management.WorkerManagementClient;
import org.ourgrid.common.interfaces.to.LocalWorkerState;
import org.ourgrid.common.interfaces.to.WorkerStatus;
import org.ourgrid.common.statistics.control.WorkerControl;
import org.ourgrid.peer.PeerConstants;
import org.ourgrid.peer.controller.allocation.RedistributionController;
import org.ourgrid.peer.controller.messages.WorkerMessages;
import org.ourgrid.peer.dao.AllocationDAO;
import org.ourgrid.peer.dao.LocalWorkersDAO;
import org.ourgrid.peer.dao.UsersDAO;
import org.ourgrid.peer.to.AllocableWorker;
import org.ourgrid.peer.to.LocalWorker;
import org.ourgrid.peer.to.PeerUserReference;
import org.ourgrid.peer.to.Request;
import org.ourgrid.reqtrace.Req;

import br.edu.ufcg.lsd.commune.api.FailureNotification;
import br.edu.ufcg.lsd.commune.api.InvokeOnDeploy;
import br.edu.ufcg.lsd.commune.api.MonitoredBy;
import br.edu.ufcg.lsd.commune.api.RecoveryNotification;
import br.edu.ufcg.lsd.commune.container.servicemanager.ServiceManager;
import br.edu.ufcg.lsd.commune.identification.DeploymentID;
import br.edu.ufcg.lsd.commune.identification.ServiceID;

/**
 * Performs Worker Management Client Receiver actions
 */
public class WorkerManagementClientController implements WorkerManagementClient {

	private ServiceManager serviceManager;

	@InvokeOnDeploy
	public void init(ServiceManager serviceManager) {
		this.serviceManager = serviceManager;
	}
	
	/**
	 * Processes the change of status of a Worker
	 * @param worker The Worker that changed status
	 * @param workerPublicKey The PublicKey of the Worker that changed Status
	 * @param status The new Status of the Worker
	 */
	@Req("REQ025")
	public void statusChangedAllocatedForBroker(
			@MonitoredBy(PeerConstants.WORKER_MANAGEMENT_CLIENT_OBJECT_NAME) Worker worker) {
		
		String workerPublicKey = serviceManager.getSenderPublicKey();
		ServiceID senderServiceID = serviceManager.getSenderServiceID();
		
		LocalWorker localWorker = WorkerControl.getInstance().getRecoveredWorker(
				senderServiceID, serviceManager.getDAO(LocalWorkersDAO.class));

		DeploymentID workerID = serviceManager.getStubDeploymentID(worker);
		
		if(localWorker == null){
			serviceManager.getLog().error(WorkerMessages.getUnknownWorkerChangingStatusMessage(workerID, 
					WorkerStatus.ALLOCATED_FOR_BROKER));
			return;
		} 

		if(!checkKey(localWorker.getPublicKey(), workerPublicKey)){
			serviceManager.getLog().error(WorkerMessages.getWrongPublicKeyForWorkerMessage(workerID));
			return;
		}
		
		switch (localWorker.getStatus()) {
		
		case IN_USE:
			
			changeStatusToAllocatedForBroker(localWorker, worker);
			break;
			
		default:
			
			serviceManager.getLog().warn(WorkerMessages.getInvalidStatusChangeMessage(
					serviceManager.getStubDeploymentID(localWorker.getWorkerManagement()),
					localWorker.getStatus()));
			return;
		}
	}

	@FailureNotification
	public void workerIsDown(Worker worker) {}

	@RecoveryNotification
	public void workerIsUp(Worker worker) {}

	private void changeStatusToAllocatedForBroker(LocalWorker localWorker, Worker worker) {
		
		DeploymentID workerManagementOID = serviceManager.getStubDeploymentID(localWorker.getWorkerManagement());
		
		if(worker == null){
			serviceManager.getLog().warn(WorkerMessages.getNullWorkerStatusChangedMessage(workerManagementOID, 
					WorkerStatus.ALLOCATED_FOR_BROKER));
			return;
		} 
		
		AllocationDAO dao = serviceManager.getDAO(AllocationDAO.class);
		AllocableWorker allocable = dao.getAllocableWorker(localWorker.getPublicKey());
		
		if (allocable.getConsumer() == null) {
			serviceManager.getLog().warn(WorkerMessages.getNullConsumerChangeStatusToAllocatedForBroker());
			return;
		}
		
		allocable.setAsDelivered();
		
		serviceManager.getLog().info(WorkerMessages.getStatusChangedMessage(workerManagementOID, 
				LocalWorkerState.IN_USE));
		serviceManager.getLog().info(WorkerMessages.getGivingWorkerMessage(workerManagementOID,
				allocable.getConsumer().getDeploymentID()));
		
		
		UsersDAO usersDAO = serviceManager.getDAO(UsersDAO.class);
		
		PeerUserReference loggedUser = usersDAO.getLoggedUser(allocable.getConsumer().getPublicKey());
		
		if (loggedUser == null) {
			serviceManager.getLog().warn(WorkerMessages.getUnknownConsumerAllocatedForBroker((allocable.getConsumer().getPublicKey())));
			return;
		}
		
		LocalWorkerProviderClient lwpc = loggedUser.getClient();

		if (lwpc != null) {
			lwpc.hereIsWorker(worker, allocable.getWorkerSpec(), allocable.getRequest().getSpec());
			
			WorkerControl.getInstance().statusChanged(
					workerManagementOID.getServiceID(), LocalWorkerState.IN_USE, 
					loggedUser.getDeploymentId().getContainerID().getUserAtServer(), serviceManager);
			//Do not unregister
			serviceManager.release(worker);
//			serviceManager.release(allocable.getWorkerManagement());
//			unregisterFailureInterested(workerMonitor, localWorker.getWorkerManagement());
		}
	}

	/**
	 * Processes the change of status of a Worker
	 * @param worker The Worker that changed status
	 * @param workerPublicKey The PublicKey of the Worker that changed Status
	 */
	@Req("REQ025")
	public void statusChangedAllocatedForPeer(
			@MonitoredBy(PeerConstants.WORKER_MANAGEMENT_CLIENT_OBJECT_NAME) RemoteWorkerManagement worker) {
		String workerPublicKey = serviceManager.getSenderPublicKey();
		ServiceID senderServiceID = serviceManager.getSenderServiceID();
		
		LocalWorker localWorker = WorkerControl.getInstance().getRecoveredWorker(
				senderServiceID, serviceManager.getDAO(LocalWorkersDAO.class));

		DeploymentID workerID = serviceManager.getStubDeploymentID(worker);
		
		if(localWorker == null){
			serviceManager.getLog().error(WorkerMessages.getUnknownWorkerChangingStatusMessage(workerID, 
					WorkerStatus.ALLOCATED_FOR_PEER));
			return;
		} 

		if(!checkKey(localWorker.getPublicKey(),workerPublicKey)){
			serviceManager.getLog().error(WorkerMessages.getWrongPublicKeyForWorkerMessage(workerID));
			return;
		} 

		switch (localWorker.getStatus()) {
		
		case DONATED:

			changeStatusToAllocatedForPeer(localWorker, worker);
			break;
			
		default:
			
			serviceManager.getLog().warn(WorkerMessages.getIgnoredStatusChangeMessage(
					serviceManager.getStubDeploymentID(localWorker.getWorkerManagement()), 
					localWorker.getStatus(), WorkerStatus.ALLOCATED_FOR_PEER));
			return;
		}
	}
	
	@FailureNotification
	public void remoteWorkerManagementIsDown(RemoteWorkerManagement worker) {}

	@RecoveryNotification
	public void remoteWorkerManagementIsUp(RemoteWorkerManagement worker) {}


	private void changeStatusToAllocatedForPeer(LocalWorker localWorker, RemoteWorkerManagement worker) {
		
		DeploymentID workerID = serviceManager.getStubDeploymentID(localWorker.getWorkerManagement());
		
		if(worker == null ){
			serviceManager.getLog().warn(WorkerMessages.getNullWorkerStatusChangedMessage(
					workerID, 
					WorkerStatus.ALLOCATED_FOR_PEER));
			return;
		}
		AllocableWorker allocable = serviceManager.getDAO(AllocationDAO.class).getAllocableWorker(localWorker.getPublicKey());
		allocable.setAsDelivered();
		
		RemoteWorkerProviderClient rwpc = (RemoteWorkerProviderClient) allocable.getConsumer().getConsumerStub();
		
		localWorker.setStatus(LocalWorkerState.DONATED);
		serviceManager.getLog().info(WorkerMessages.getStatusChangedMessage(workerID, LocalWorkerState.DONATED));
		serviceManager.getLog().info(WorkerMessages.getDonatingWorkerMessage(workerID, allocable.getConsumer().getDeploymentID()));
		
		RemoteWorkerProvider rwp = (RemoteWorkerProvider) serviceManager.getObjectDeployment(
				PeerConstants.REMOTE_ACCESS_OBJECT_NAME).getObject();
		
		rwpc.hereIsWorker(rwp, worker, allocable.getWorkerSpec());
		
		WorkerControl.getInstance().statusChanged(workerID.getServiceID(), 
				LocalWorkerState.DONATED,
				allocable.getConsumer().getDeploymentID().getContainerID().getUserAtServer(),
				serviceManager);
		
		//Do not unregister
//		unregisterFailureInterested(workerMonitor, localWorker.getWorkerManagement());
	}

	/**
	 * Processes the change of status of a Worker
	 * @param worker The Worker that changed status
	 * @param workerPublicKey The PublicKey of the Worker that changed Status
	 * @param status The new Status of the Worker
	 */
	@Req("REQ025")
	public void statusChanged(WorkerStatus status) {
		String workerPublicKey = serviceManager.getSenderPublicKey();
		ServiceID senderServiceID = serviceManager.getSenderServiceID();
		
		LocalWorker localWorker = WorkerControl.getInstance().getRecoveredWorker(
				senderServiceID, serviceManager.getDAO(LocalWorkersDAO.class));

		if(localWorker == null){
			serviceManager.getLog().warn(WorkerMessages.getUnknownWorkerChangingStatusMessage(workerPublicKey, status));
			return;
		} 
		
		if(!checkKey(localWorker.getPublicKey(), workerPublicKey)){
			DeploymentID workerID = serviceManager.getStubDeploymentID(localWorker.getWorkerManagement());
			
			serviceManager.getLog().error(WorkerMessages.getWrongPublicKeyForWorkerMessage(workerID));
			return;
		} 

		switch ( status ) {
		case IDLE:
			
			changeStatusIdle(localWorker);
			break;
		
		case OWNER:
			changeStatusOwner(localWorker);
			break;
		
			
		default:
			break;
		}
	}

	private void changeStatusIdle(LocalWorker localWorker) {
	
		switch (localWorker.getStatus()) {
		case IDLE:
		case OWNER:
		case DONATED:
		case IN_USE:
			
			RedistributionController.getInstance().createAllocableWorker(localWorker, serviceManager);
				
			WorkerControl.getInstance().statusChanged(
					localWorker.getServiceID(), 
					LocalWorkerState.IDLE, 
					serviceManager);
				
			DeploymentID workerManagementOID = serviceManager.getStubDeploymentID(localWorker.getWorkerManagement());
			serviceManager.getLog().debug(WorkerMessages.getStatusChangedMessage(
					workerManagementOID, 
					LocalWorkerState.IDLE));
			
			RedistributionController.getInstance().redistributeIdleWorker(localWorker, serviceManager);
			break;

		default:
			DeploymentID workerID = serviceManager.getStubDeploymentID(localWorker.getWorkerManagement());
			
			serviceManager.getLog().warn(WorkerMessages.getIgnoredStatusChangeMessage(workerID, 
					localWorker.getStatus(), WorkerStatus.IDLE));
		
		}
	}

	private void changeStatusOwner(LocalWorker localWorker) {
		
		if(!localWorker.getStatus().isOwner()){
			
			DeploymentID workerID = serviceManager.getStubDeploymentID(localWorker.getWorkerManagement());
			
			serviceManager.getLog().info(WorkerMessages.getStatusChangedMessage(workerID, 
					LocalWorkerState.OWNER));
			
			AllocationDAO dao = serviceManager.getDAO(AllocationDAO.class);
			String publicKey = workerID.getPublicKey();
			AllocableWorker localAllocableWorker = dao.getAllocableWorker(publicKey);
			
			Request request = localAllocableWorker.getRequest();
			if (request != null) {
				request.removeAllocableWorker(localAllocableWorker);
			}
			
			dao.removeLocalAllocableWorker(publicKey);
			localWorker.setStatus(LocalWorkerState.OWNER);
			
			WorkerControl.getInstance().statusChanged(workerID.getServiceID(), 
					LocalWorkerState.OWNER, serviceManager);
		}
	}

}